{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "71a329f0",
   "metadata": {},
   "source": [
    "# LLAMA 7B with customized stop reasons\n",
    "In this tutorial, you will use LMI container from DLC to SageMaker and run inference with it.\n",
    "\n",
    "Please make sure the following permission granted before running the notebook:\n",
    "\n",
    "- S3 bucket push access\n",
    "- SageMaker access\n",
    "\n",
    "## Step 1: Let's bump up SageMaker and import stuff"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67fa3208",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install sagemaker --upgrade  --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec9ac353",
   "metadata": {},
   "outputs": [],
   "source": [
    "import boto3\n",
    "import sagemaker\n",
    "from sagemaker import Model, image_uris, serializers, deserializers\n",
    "\n",
    "role = sagemaker.get_execution_role()  # execution role for the endpoint\n",
    "sess = sagemaker.session.Session()  # sagemaker session for interacting with different AWS APIs\n",
    "region = sess._region_name  # region name of the current SageMaker Studio environment\n",
    "account_id = sess.account_id()  # account_id of the current SageMaker Studio environment"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81deac79",
   "metadata": {},
   "source": [
    "## Step 2: Start preparing model artifacts\n",
    "In LMI contianer, we expect some artifacts to help setting up the model\n",
    "- serving.properties (required): Defines the model server settings\n",
    "- model.py (optional): A python file to define the core inference logic\n",
    "- requirements.txt (optional): Any additional pip wheel need to install"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b011bf5f",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile serving.properties\n",
    "engine=MPI\n",
    "option.model_id=TheBloke/Llama-2-7B-fp16\n",
    "option.tensor_parallel_degree=1\n",
    "option.max_rolling_batch_size=32\n",
    "option.rolling_batch=lmi-dist\n",
    "option.output_formatter=jsonlines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0142973",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%sh\n",
    "mkdir mymodel\n",
    "mv serving.properties mymodel/\n",
    "mv model.py mymodel/\n",
    "tar czvf mymodel.tar.gz mymodel/\n",
    "rm -rf mymodel"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e58cf33",
   "metadata": {},
   "source": [
    "## Step 3: Start building SageMaker endpoint\n",
    "In this step, we will build SageMaker endpoint from scratch"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d955679",
   "metadata": {},
   "source": [
    "### Getting the container image URI\n",
    "\n",
    "[Large Model Inference available DLC](https://github.com/aws/deep-learning-containers/blob/master/available_images.md#large-model-inference-containers)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a174b36",
   "metadata": {},
   "outputs": [],
   "source": [
    "image_uri = image_uris.retrieve(\n",
    "        framework=\"djl-deepspeed\",\n",
    "        region=sess.boto_session.region_name,\n",
    "        version=\"0.27.0\"\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11601839",
   "metadata": {},
   "source": [
    "### Upload artifact on S3 and create SageMaker model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38b1e5ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "s3_code_prefix = \"large-model-lmi/code\"\n",
    "bucket = sess.default_bucket()  # bucket to house artifacts\n",
    "code_artifact = sess.upload_data(\"mymodel.tar.gz\", bucket, s3_code_prefix)\n",
    "print(f\"S3 Code or Model tar ball uploaded to --- > {code_artifact}\")\n",
    "\n",
    "model = Model(image_uri=image_uri, model_data=code_artifact, role=role)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "004f39f6",
   "metadata": {},
   "source": [
    "### 4.2 Create SageMaker endpoint\n",
    "\n",
    "You need to specify the instance to use and endpoint names"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e0e61cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "instance_type = \"ml.g5.2xlarge\"\n",
    "endpoint_name = sagemaker.utils.name_from_base(\"lmi-model\")\n",
    "\n",
    "model.deploy(initial_instance_count=1,\n",
    "             instance_type=instance_type,\n",
    "             endpoint_name=endpoint_name,\n",
    "            )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb63ee65",
   "metadata": {},
   "source": [
    "## Step 5: Test and benchmark the inference"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3a36ceb",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LineIterator:\n",
    "    \n",
    "    def __init__(self, stream):\n",
    "        self.byte_iterator = iter(stream)\n",
    "        self.buffer = io.BytesIO()\n",
    "        self.read_pos = 0\n",
    "\n",
    "    def __iter__(self):\n",
    "        return self\n",
    "\n",
    "    def __next__(self):\n",
    "        while True:\n",
    "            self.buffer.seek(self.read_pos)\n",
    "            line = self.buffer.readline()\n",
    "            if line and line[-1] == ord('\\n'):\n",
    "                self.read_pos += len(line)\n",
    "                return line[:-1]\n",
    "            try:\n",
    "                chunk = next(self.byte_iterator)\n",
    "            except StopIteration:\n",
    "                if self.read_pos < self.buffer.getbuffer().nbytes:\n",
    "                    continue\n",
    "                raise\n",
    "            if 'PayloadPart' not in chunk:\n",
    "                print('Unknown event type:' + chunk)\n",
    "                continue\n",
    "            self.buffer.seek(0, io.SEEK_END)\n",
    "            self.buffer.write(chunk['PayloadPart']['Bytes'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2bcef095",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json, io\n",
    "sm_client = boto3.client(\"sagemaker-runtime\")\n",
    "body = {\"inputs\": \"what is life\", \"parameters\": {\"max_new_tokens\":25, \"do_sample\": True,  \"details\": True}}\n",
    "resp = sm_client.invoke_endpoint_with_response_stream(EndpointName=endpoint_name, Body=json.dumps(body), ContentType=\"application/json\")\n",
    "event_stream = resp['Body']\n",
    "\n",
    "for line in LineIterator(event_stream):\n",
    "    resp = json.loads(line)\n",
    "    print(resp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3012dddd",
   "metadata": {},
   "source": [
    "Let's try to make it continue generate based on finish reason"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41ffb541",
   "metadata": {},
   "outputs": [],
   "source": [
    "def inference(payload):\n",
    "    resp = sm_client.invoke_endpoint_with_response_stream(EndpointName=endpoint_name, Body=json.dumps(payload), ContentType=\"application/json\")\n",
    "    event_stream = resp['Body']\n",
    "    text_output = []\n",
    "    for line in LineIterator(event_stream):\n",
    "        resp = json.loads(line)\n",
    "        token = resp['token']['text']\n",
    "        text_output.append(token)\n",
    "        print(token, end='')\n",
    "        if resp['details']:\n",
    "            finish_reason = resp['details']['finish_reason']\n",
    "            return payload['inputs'] + ''.join(text_output), finish_reason, len(text_output)\n",
    "\n",
    "payload = {\"inputs\": \"The new movie that got Oscar this year\", \"parameters\": {\"max_new_tokens\":128, \"do_sample\": True, \"top_p\": 0.9,\n",
    "           \"temperature\": 0.8, \"repetition_penalty\": 1.2, \"details\": True}}\n",
    "\n",
    "finish_reason = \"length\"\n",
    "print(f\"Ouput: {payload['inputs']}\", end='')\n",
    "total_tokens = 0\n",
    "total_query = 0\n",
    "while finish_reason == 'length':\n",
    "    output_text, finish_reason, out_token_len = inference(payload)\n",
    "    payload['inputs'] = output_text\n",
    "    total_tokens += out_token_len\n",
    "    total_query += 1\n",
    "print(f\"\\ntotal token generated: {total_tokens} total query sent: {total_query}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1cd9042",
   "metadata": {},
   "source": [
    "## Clean up the environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d674b41",
   "metadata": {},
   "outputs": [],
   "source": [
    "sess.delete_endpoint(endpoint_name)\n",
    "sess.delete_endpoint_config(endpoint_name)\n",
    "model.delete_model()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
